﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Reflection;
using System.Collections;
using ProgNetComponents.Interfaces;

namespace ProgNetComponents.Design.Attributes
{
    /// <summary>
    /// Class that will return different attribute base on some conditions
    /// </summary>
    [AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]
    public abstract class DynamicPropertyAttribute : Attribute, IDynamicAttribute
    {
        #region oryginal attributes
        private static Dictionary<Type, PropertAttributesCollection> OrgAttribs = new Dictionary<Type, PropertAttributesCollection>();
        private class PropertAttributesClass : INamedObject
        {
            public PropertAttributesClass(string name, params Attribute[] attribs)
            {
                Name = name;
                Attributes = new List<Attribute>();
                if (attribs != null)
                    Attributes.AddRange(attribs);
            }

            public string Name
            {
                get;
                set;
            }

            public List<Attribute> Attributes
            {
                get;
                private set;
            }
        }
        private class PropertAttributesCollection : ProgNetComponents.Collections.BaseNamedCollection<PropertAttributesClass>
        {

        }
        /// <summary>
        /// Returns TypeDescriptor.GetProperties(component, true)
        /// </summary>
        /// <param name="component"></param>
        /// <returns></returns>
        public static PropertyDescriptorCollection GetOldProperties(object component)
        {
            return TypeDescriptor.GetProperties(component, true);
        }
        #endregion

        /// <summary>
        /// Gets updated properties for a component
        /// </summary>
        /// <param name="component"></param>
        /// <returns></returns>
        public static PropertyDescriptorCollection GetProperties(object component)
        {
            PropertyDescriptorCollection old = GetOldProperties(component);
          
            PropertyInfo[] propsInfo = null;
            List<PropertyInfo> pList = new List<PropertyInfo>();
            Type t = component.GetType();
            List<string> strList = new List<string>();

            List<PropertyDescriptor> list = new List<PropertyDescriptor>();
            List<Attribute> attribs = new List<Attribute>();
            PropertAttributesCollection c;
            if (!OrgAttribs.ContainsKey(t))
            {
                c = new PropertAttributesCollection();
                OrgAttribs.Add(t, c);
                PropertAttributesClass cl;

                List<Attribute> l = new List<Attribute>();
                Attribute ai;
                for (int i = 0; i < old.Count; i++)
                {
                    l.Clear();
                    for (int j = 0; j < old[i].Attributes.Count; j++)
                    {
                        ai = old[i].Attributes[j];
                        if (ai is IDynamicAttribute || ai is BrowsableAttribute)
                            continue;
                        l.Add(ai);
                    }
                    cl = new PropertAttributesClass(old[i].Name, l.ToArray());
                }
            }
            else
            {
                c = OrgAttribs[t];
            }

            while (t != typeof(Object))
            {
                propsInfo = t.GetProperties(BindingFlags.Public | BindingFlags.Instance);
                pList.Clear();
                pList.AddRange(propsInfo);
               
                foreach (PropertyInfo prop in pList)
                {
                    if (component is IComponent && prop.Name == "Name")
                        continue;

                    if (!strList.Contains(prop.Name))
                    {
                        attribs.Clear();
                        foreach (Attribute attrib in prop.GetCustomAttributes(true))
                        {
                            attribs.Add(attrib);
                        }
                        strList.Add(prop.Name);
                        ArrayList attributeList = new ArrayList();
                        bool changedAttribute = false;
                        bool isBrowsable = true;

                        Attribute a;

                        foreach (Attribute attrib in attribs)
                        {
                            if (attrib is BrowsableAttribute)
                            {
                                if (!((BrowsableAttribute)attrib).Browsable)
                                    isBrowsable = false;
                            }
                            if (attrib is IDynamicAttribute)
                            {
                                a = ((IDynamicAttribute)attrib).GetRealAttribute(component);

                                if (a is BrowsableAttribute)
                                {
                                    if (!((BrowsableAttribute)a).Browsable)
                                        isBrowsable = false;
                                }
                                else
                                {
                                    attributeList.Add(a);
                                }
                                changedAttribute = true;
                            }
                            else
                                attributeList.Add(attrib);
                        }
                        if (!isBrowsable && changedAttribute)
                            attributeList.Add(new BrowsableAttribute(false));
                       
                        if (changedAttribute)
                        {
                            if (c[prop.Name] != null)
                                attributeList.AddRange(c[prop.Name].Attributes);
                            list.Add(new PropertyInfoDescriptor(prop, (Attribute[])attributeList.ToArray(typeof(Attribute))));
                        }
                    }
                }
                t = t.BaseType;
            }
            for (int i = 0; i < old.Count; i++)
            {
                bool found = false;
                for (int j = 0; j < list.Count; j++)
                {
                    if (list[j].Name == old[i].Name)
                    {
                        found = true;
                        break;
                    }
                }
                if (!found)
                    list.Add(old[i]);
            }
           return new PropertyDescriptorCollection(list.ToArray());
        }
       
        /// <summary>
        /// Property name
        /// </summary>
        protected string Property;
        /// <summary>
        /// Values
        /// </summary>
        protected object[] Values;
        /// <summary>
        /// Creates DynamicPropertyAttribute instance
        /// </summary>
        /// <param name="property"></param>
        /// <param name="values"></param>
        protected DynamicPropertyAttribute(string property, params object[] values)
        {
            this.Property = property;
            if (values != null)
                this.Values = values;
            else
            {
                this.Values = new object[] { null };
            }
        }

        /// <summary>
        /// Gets real attribute
        /// </summary>
        /// <param name="target"></param>
        /// <returns></returns>
        public virtual Attribute GetRealAttribute(object target)
        {
            object obj = null;
            PropertyInfo pi = target.GetSingleProperty(Property);
            if (pi != null)
                obj = pi.GetValue(target, null);
            else
            {
                FieldInfo fi = target.GetSingleField(Property);
                if (fi != null)
                    obj = fi.GetValue(target);
            }
            return GetAttribute(GetValuesMatch(Values, obj));
        }
        /// <summary>
        /// Gets if val is equal to any of specified values
        /// </summary>
        /// <param name="defVal"></param>
        /// <param name="val"></param>
        /// <returns></returns>
        protected virtual bool GetValuesMatch(object[] defVal, object val)
        {
            if (val == null && defVal == null)
                return true;
            else if (defVal == null)
            {
                return false;
            }
            else
            {
                foreach (object o in defVal)
                {
                    if (val.AsString() == o.AsString())
                        return true;
                }
            }
            return false;
        }
        /// <summary>
        /// Gets real attribute base on valuesMatch
        /// </summary>
        /// <param name="valuesMatch"></param>
        /// <returns></returns>
        protected abstract Attribute GetAttribute(bool valuesMatch);
        
    }


    
}
